package ru.usu.gv.utils.math;

import javax.vecmath.Point3d;

import java.util.ArrayList;
import java.util.List;

/**
 * @author spupyrev
 * 08.11.2009
 */
public class Matrix
{
	private double[][] data;

	public Matrix(Matrix other)
	{
		this(other.data);
	}

	public Matrix(double[][] data)
	{
		setData(data);
	}

	public void setData(double[][] data)
	{
		this.data = new double[data.length][];
		for (int i = 0; i < data.length; i++)
		{
			this.data[i] = new double[data[i].length];
			for (int j = 0; j < data[i].length; j++)
				this.data[i][j] = data[i][j];
		}
	}

	public Matrix(int rowCount, int columnCount)
	{
		this(rowCount, columnCount, 0);
	}

	public Matrix(int rowCount, int columnCount, double value)
	{
		data = new double[rowCount][columnCount];
		for (int i = 0; i < rowCount; i++)
			for (int j = 0; j < columnCount; j++)
				data[i][j] = value;
	}

	public int getRowCount()
	{
		return data.length;
	}

	public int getColumnCount()
	{
		return (getRowCount() == 0 ? 0 : (int) data[0].length);
	}

	public double get(int i, int j)
	{
		return data[i][j];
	}

	public void set(int i, int j, double value)
	{
		data[i][j] = value;
	}

	public double[][] getData()
	{
		return data;
	}

	//Multiplies a matrix with a scalar factor
	public void scale(double factor)
	{
		for (int i = 0; i < getRowCount(); i++)
			for (int j = 0; j < getColumnCount(); j++)
				data[i][j] *= factor;

	}

	//Squares all entries of a matrix
	public void squareEntries()
	{
		for (int i = 0; i < getRowCount(); i++)
			for (int j = 0; j < getColumnCount(); j++)
				data[i][j] *= data[i][j];
	}

	//Returns the sum of all entries
	public double sum()
	{
		double sum = 0;
		for (int i = 0; i < getRowCount(); i++)
			for (int j = 0; j < getColumnCount(); j++)
				sum += data[i][j];

		return sum;
	}

	//Returns the mean of i-th row
	public double meanRow(int i)
	{
		double sum = 0;
		for (int k = 0; k < getColumnCount(); k++)
			sum += data[i][k];

		if (getColumnCount() > 0)
			sum /= getColumnCount();

		return sum;
	}

	//Returns the mean of j-th column
	public double meanColumn(int j)
	{
		double sum = 0;
		for (int k = 0; k < getRowCount(); k++)
			sum += data[k][j];

		if (getRowCount() > 0)
			sum /= getRowCount();

		return sum;
	}

	//Double-centers a matrix in such a way that the center of gravity is zero
	//After double-centering, each row and each column sums up to zero
	public void doubleCenter()
	{
		if (getRowCount() == 0 || getColumnCount() == 0)
			return;

		double globalMean = sum() / ((double) getRowCount() * getColumnCount());
		List<Double> rowMean = new ArrayList();
		List<Double> columnMean = new ArrayList();
		for (int i = 0; i < getRowCount(); i++)
			rowMean.add(meanRow(i));
		for (int j = 0; j < getColumnCount(); j++)
			columnMean.add(meanColumn(j));

		for (int i = 0; i < getRowCount(); i++)
			for (int j = 0; j < getColumnCount(); j++)
				data[i][j] -= rowMean.get(i) + columnMean.get(j) - globalMean;
	}

	//Multiply a square matrix and a vector. 
	//Note that matrix width and vector length
	//have to be equal, otherwise null is returned.
	public Vector multiply(Vector x)
	{
		assert (getColumnCount() == x.getCount());
		Vector y = new Vector(x.getCount());
		for (int i = 0; i < getRowCount(); i++)
		{
			for (int j = 0; j < getColumnCount(); j++)
			{
				y.set(i, y.get(i) + data[i][j] * x.get(j));
			}
		}

		return y;
	}

	//Multiply a square matrix and a vector. 
	//Note that matrix width and vector length
	//have to be equal, otherwise null is returned.
	public void multiply(Matrix other)
	{
		assert (getColumnCount() == other.getRowCount());
		double[][] res = new double[getRowCount()][other.getColumnCount()];
		for (int i = 0; i < getRowCount(); i++)
			for (int j = 0; j < other.getColumnCount(); j++)
			{
				res[i][j] = 0;
				for (int k = 0; k < getColumnCount(); k++)
					res[i][j] += data[i][k] * other.data[k][j];
			}

		setData(res);
	}

	@Override
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < getRowCount(); i++)
		{
			for (int j = 0; j < getColumnCount(); j++)
				sb.append(String.format("%.3f ", data[i][j]));
			sb.append("\n");
		}
		return sb.toString();
	}

	public void subtract(Matrix other)
	{
		assert (getRowCount() == other.getRowCount());
		assert (getColumnCount() == other.getColumnCount());

		for (int i = 0; i < getRowCount(); i++)
			for (int j = 0; j < getColumnCount(); j++)
				data[i][j] -= other.data[i][j];
	}

	public void add(Matrix other)
	{
		assert (getRowCount() == other.getRowCount());
		assert (getColumnCount() == other.getColumnCount());

		for (int i = 0; i < getRowCount(); i++)
			for (int j = 0; j < getColumnCount(); j++)
				data[i][j] += other.data[i][j];
	}

	public void inverse()
	{
		Jama.Matrix jm = new Jama.Matrix(data);
		jm = jm.inverse();
		setData(jm.getArray());
	}

	public Point3d getRowAsPoint(int rowIndex)
	{
		int cc = getColumnCount();
		assert (1 <= cc && cc <= 3);
		Point3d res = new Point3d();
		if (cc >= 1)
			res.x = get(rowIndex, 0);
		if (cc >= 2)
			res.y = get(rowIndex, 1);
		if (cc >= 3)
			res.z = get(rowIndex, 2);
		return res;
	}
}
